package com.jeasonzhao.model;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;

import com.jeasonzhao.commons.basic.IntegerPair;
import com.jeasonzhao.commons.basic.IntegerPairHelper;
import com.jeasonzhao.commons.basic.StringPair;
import com.jeasonzhao.commons.db.SqlParameter;
import com.jeasonzhao.commons.db.Sqlca;
import com.jeasonzhao.commons.json.JSONArray;
import com.jeasonzhao.commons.json.JSONClass;
import com.jeasonzhao.commons.json.JSONConstant;
import com.jeasonzhao.commons.json.JSONException;
import com.jeasonzhao.commons.json.JSONNode;
import com.jeasonzhao.commons.utils.Algorithms;
import com.jeasonzhao.commons.utils.DataTypes;
import com.jeasonzhao.commons.xml.XMLException;
import com.jeasonzhao.commons.xml.XMLHelper;
import com.jeasonzhao.commons.xml.XMLNode;

public class ModelHelper
{
    private static final String FUN_AFTERINITIALIZEDFROMXML = "afterInitializedFromXML";
    private static final String FUN_AFTERINITIALIZEDFROMJSON = "afterInitializedFromJSON";
    private static final String FUN_NORMALIZEMODELITEM = "normalizeModelItem";
    private static final String FUN_AFTERCONVERTTOXML = "afterConvertToXML";
    private static final String FUN_AFTERCONVERTTOJSON = "afterConvertToJSON";
    private static final String FUN_AFTERINITIALIZEDFROMDATABASE = "afterInitializedFromDatabase";

    @SuppressWarnings("rawtypes")
    private java.util.Hashtable<Class,MappedInformation> m_allCachedClass = new java.util.Hashtable<Class,MappedInformation>();

    @SuppressWarnings("rawtypes")
    private java.util.Hashtable<Class,IModelClassWrapper> m_allCachedClassWraper = new java.util.Hashtable<Class,IModelClassWrapper>();

    private java.util.Hashtable<String,String> m_allCollectionClassMapping = null;
    private final IModelMetaDataGenerator m_metaDataGenerator = new DefaultModelMetaGenerator();

    @SuppressWarnings("rawtypes")
    private ModelHelper()
    {
        m_allCachedClass = new java.util.Hashtable<Class,MappedInformation>();
        m_allCollectionClassMapping = new Hashtable<String,String>();
        //Add commons collections
        m_allCollectionClassMapping.put(com.jeasonzhao.commons.basic.StringCollection.class.getName()
                                        ,String.class.getName());
        m_allCollectionClassMapping.put(com.jeasonzhao.commons.basic.StringPairCollection.class.getName()
                                        ,com.jeasonzhao.commons.basic.StringPair.class.getName());
        m_allCollectionClassMapping.put(com.jeasonzhao.commons.basic.IntegerPairCollection.class.getName()
                                        ,com.jeasonzhao.commons.basic.IntegerPair.class.getName());
    }

    @SuppressWarnings("rawtypes")
    public void registerClassWrapper(Class<?> cls,IModelClassWrapper w)
    {
        if(null != cls && null != w)
        {
            if(null == m_allCachedClassWraper)
            {
                m_allCachedClassWraper = new java.util.Hashtable<Class,IModelClassWrapper>();
            }
            m_allCachedClassWraper.put(cls,w);
        }
    }

    public static final boolean isModelItem(Class<?> cls)
    {
        return null != cls && IModelItem.class.isAssignableFrom(cls);
    }

    public static final boolean isExModelItem(Class<?> cls)
    {
        return null != cls && IModelItem.class.isAssignableFrom(cls);
    }

    public static final boolean isModelTagged(Class<?> cls)
    {
        return
            null != cls &&
            null != cls.getAnnotation(com.jeasonzhao.model.Model.class);
    }

    public static final boolean isModelTagged(Field field)
    {
        return null != field &&
            null != field.getAnnotation(com.jeasonzhao.model.Model.class);
    }

    private MappedInformation getMappedInformation(Class<?> cls,JSONClass jsomItemScript)
        throws ModelException
    {
        if(null == cls)
        {
            return null;
        }
        Class<?> clsNew = null;
        if(null != this.m_allCachedClassWraper)
        {
            IModelClassWrapper clw = m_allCachedClassWraper.get(cls);
            if(null != clw)
            {
                clsNew = clw.getClass(cls,jsomItemScript);
            }
        }
        if(null != clsNew)
        {
            return getMappedInformation(clsNew);
        }
        else
        {
            return getMappedInformation(cls);
        }
    }

    public boolean isRegistered(Class<?> cls)
    {
        try
        {
            return getMappedInformation(cls) != null;
        }
        catch(ModelException ex)
        {
            return false;
        }
    }

    public MappedInformation getMappedInformation(Class<?> cls,XMLNode node)
        throws ModelException
    {
        if(null == cls)
        {
            return null;
        }
        Class<?> clsNew = null;
        if(null != this.m_allCachedClassWraper)
        {
            IModelClassWrapper clw = m_allCachedClassWraper.get(cls);
            if(null != clw)
            {
                clsNew = clw.getClass(cls,node);
            }
        }
        if(null != clsNew)
        {
            return getMappedInformation(clsNew);
        }
        else
        {
            return getMappedInformation(cls);
        }
    }

    public MappedInformation getMappedInformation(Class<?> cls)
        throws ModelException
    {
        if(null == cls)
        {
            throw new ModelException.CreateFromNullClass();
        }
        if(false == this.m_allCachedClass.containsKey(cls))
        {
            synchronized(m_allCachedClass)
            {
                if(false == this.m_allCachedClass.containsKey(cls))
                {
                    m_allCachedClass.put(cls,m_metaDataGenerator.generateClassInformation(cls));
                }
            }
        }
        if(false == this.m_allCachedClass.containsKey(cls))
        {
            throw new ModelException.NewInstanceError(cls);
        }
        else
        {
            return m_allCachedClass.get(cls);
        }
    }

    public ModelHelper registerCollectionSupport(Class<?> clsCollection,Class<?> clsItem)
    {
        if(null == clsCollection || null == clsItem)
        {
            return this;
        }
        else
        {
            return this.registerCollectionSupport(clsCollection.getName(),clsItem.getName());
        }
    }

    public ModelHelper registerCollectionSupport(String strCollectionClass,String strItemClass)
    {
        if(Algorithms.isEmpty(strCollectionClass) || Algorithms.isEmpty(strItemClass))
        {
            return this;
        }
        if(null == m_allCollectionClassMapping)
        {
            m_allCollectionClassMapping = new Hashtable<String,String>();
        }
        m_allCollectionClassMapping.put(strCollectionClass,strItemClass);
        return this;
    }

    public String getComponentClassFromCollectionClass(String str)
    {
        return null == m_allCollectionClassMapping ? null : m_allCollectionClassMapping.get(str);
    }

    private final static ModelHelper m_instance = new ModelHelper();
    public static ModelHelper getInstance()
    {
        return m_instance;
    }

    private void setFieldValue(Object obj,MappedFieldItem map,Object value,boolean wrap)
        throws ModelException
    {
        if(null == obj || null == map || null == value)
        {
            return;
        }
        if(wrap)
        {
            if(IntegerPair.class.isAssignableFrom(map.getType()) && null != value)
            {
                value = IntegerPair.findConstant(map.getType(),value.toString(),null);
            }
            else
            {
                value = Algorithms.castValue(map.getType(),value);
            }
        }
        //System.out.println(map.getName() + "=" + value);
        map.setValue(obj,value);
    }

    //It is only for unit-testing that marked this method as a public method.
    public String getValueString(Object obj)
    {
        if(null == obj)
        {
            return null;
        }
        if(IntegerPair.class.isAssignableFrom(obj.getClass()))
        {
            return IntegerPairHelper.getConstantFieldName((IntegerPair) obj);
        }
        else
        {
            return Algorithms.toString(obj).trim();
        }
    }

    public Object constructorNewInstance(Class<?> cls)
        throws ModelException
    {
        if(null == cls)
        {
            return null;
        }
        try
        {
            return cls.newInstance();
        }
        catch(IllegalAccessException ex)
        {
            throw new ModelException.NewInstanceError(cls,ex);
        }
        catch(InstantiationException ex)
        {
            throw new ModelException.NewInstanceError(cls,ex);
        }
    }

    @SuppressWarnings("unchecked")
    public <T> T fromXML(Class<?> cls,String strXML)
        throws ModelException
    {
        try
        {
            return(T) fromXML(cls,XMLHelper.fromString(strXML));
        }
        catch(XMLException ex)
        {
            throw new ModelException.XMLConvertFailed(ex);
        }
    }

    @SuppressWarnings(
        {"unchecked","rawtypes"})
    public <T> T fromXML(Class<?> clsInit,XMLNode node)
        throws ModelException
    {
        if(null == clsInit)
        {
            throw new ModelException.CreateFromNullClass();
        }
        if(null == node)
        {
            return null;
        }
        MappedInformation info = getMappedInformation(clsInit,node);
        if(null == info)
        {
            throw new ModelException.MappingFailed(clsInit);
        }
        Object item = constructorNewInstance(info.getOriginalClass());
        for(MappedFieldItem map : info.getMappedItems())
        {
//            log.debug("    "+map.getOwnerClass().getName()+"        "+map.getName()+"\r\n");
            Class<?> componentClass = map.getComponentClass();
            if(false == map.isCollection() || null == componentClass) //Normal field
            {
                if(map.isXMLNode())
                {
                    setFieldValue(item,map,node.selectNode(map.getNames()),false);
                }
                else
                {
                    XMLNode subNode = node.selectNode(map.getNames());
//                    log.debug("subNode="+subNode);
                    if(null != subNode)
                    {
                        Object obj = createSingleObject(map.getType(),subNode);
                        if(null == obj)
                        {
                            obj = node.subItemOrValueOf(map.getNames());
                        }
                        setFieldValue(item,map,obj,true);
                    }
                    else
                    {
                        //System.out.println("----"+Algorithms.toString(map.getNamesArray()));
                        String v = node.subItemOrValueOf(map.getNames());
                        setFieldValue(item,map,v,true);
                    }
                }
            }
            else //map.isCollectionNode()
            {
                Collection collection = (Collection)this.constructorNewInstance(map.getType());
                for(XMLNode subNode : node.selectNodes(map.getNames()))
                {
                    collection.add(createSingleObject(componentClass,subNode));
                }
                if(collection.size() > 0)
                {
                    setFieldValue(item,map,collection,false);
                }
            }
        }
        if(item instanceof IExModelItem)
        {
            ((IExModelItem) item).afterInitializedFromXML(node);
            ((IExModelItem) item).normalizeModelItem();
        }
        else
        {
            //Risk way
            callUnknownMethod(item,FUN_AFTERINITIALIZEDFROMXML,node);
            callUnknownMethod(item,FUN_NORMALIZEMODELITEM,null);
        }
        return(T) item;
    }

    private Object createSingleObject(Class<?> cls,XMLNode node)
        throws ModelException
    {
        if(null == node || null == cls)
        {
            return null;
        }
        if(isModelItem(cls) || isModelTagged(cls))
        {
            return fromXML(cls,node);
        }
        else if(StringPair.class.isAssignableFrom(cls))
        {
            String strK = node.subItemOrValueOf("key","id","name");
            String strV = node.subItemOrValueOf("value","title");
            if(null == strK || strV == null)
            {
                return null;
            }
            return new StringPair(strK,strV);
        }
        else
        {
            String strValue = node.subItemOrValueOf("value");
            return Algorithms.castValue(cls,strValue);
        }
    }

    private void callUnknownMethod(Object obj,String strName,Object theFirstParameter)
        throws ModelException
    {
        callUnknownMethod(obj,null == obj ? null : obj.getClass(),strName,theFirstParameter);
    }

    private void callUnknownMethod(Object objValue,Class<?> cls,String strName,Object theFirstParameter)
        throws ModelException
    {
        if(null == objValue
           || null == cls
           || Algorithms.isEmpty(strName)
           || Object.class.equals(cls)
           || Class.class.equals(cls)
            )
        {
            return;
        }
        Class<?> superClass = cls.getSuperclass();
        if(null != superClass)
        {
            callUnknownMethod(objValue,superClass,strName,theFirstParameter);
        }
        try
        {
            if(null != theFirstParameter)
            {
                java.lang.reflect.Method m = cls.getDeclaredMethod(strName,theFirstParameter.getClass());
                if(null != m)
                {
                    m.setAccessible(true);
                    m.invoke(objValue,theFirstParameter);
                }
            }
            else
            {
                java.lang.reflect.Method m = cls.getMethod(strName);
                if(null != m)
                {
                    m.setAccessible(true);
                    m.invoke(objValue);
                }
            }
        }
        catch(InvocationTargetException ex)
        {
        }
        catch(IllegalArgumentException ex)
        {
        }
        catch(IllegalAccessException ex)
        {
        }
        catch(SecurityException ex)
        {
        }
        catch(NoSuchMethodException ex)
        {
        }
    }

    @SuppressWarnings("unchecked")
    public <T> T fromJSON(Class<?> cls,String jsomItemScript)
        throws ModelException
    {
        try
        {
            return(T) fromJSON(cls,JSONClass.from(jsomItemScript));
        }
        catch(JSONException ex)
        {
            throw new ModelException.JSONConvertFailed(ex);
        }
    }

    @SuppressWarnings(
        {"rawtypes","unchecked"})
    public <T> T fromJSON(Class clsInit,JSONClass jsomItemScript)
        throws ModelException
    {
        if(null == clsInit || null == jsomItemScript)
        {
            return null;
        }
        MappedInformation info = getMappedInformation(clsInit,jsomItemScript);
        if(null == info.getMappedItems() || info.getMappedItems().size() < 1)
        {
            throw new ModelException.NoFieldMapped(clsInit);
        }
        Object item = constructorNewInstance(info.getOriginalClass());
        for(MappedFieldItem map : info.getMappedItems())
        {
            if(map.isCollection())
            {
                JSONArray ary = jsomItemScript.arrayNode(map.getCollectionName());
                if(null == ary)
                {
                    continue;
                }
                Class<?> componentClass = map.getComponentClass();
                Collection collection = (Collection)this.constructorNewInstance(map.getType());
                for(JSONNode jsonnode : ary.items())
                {
                    if(ModelHelper.isModelItem(componentClass) || ModelHelper.isModelTagged(componentClass))
                    {
                        collection.add(this.fromJSON(componentClass,(JSONClass) jsonnode));
                    }
                    else if(StringPair.class.isAssignableFrom(componentClass))
                    {
                        JSONClass clsNode = (JSONClass) jsonnode;
                        collection.add(new StringPair(clsNode.valueOf("key","id","name"),clsNode.valueOf("value","title")));
                    }
                    else
                    {
                        collection.add(Algorithms.castValue(componentClass,((JSONConstant) jsonnode).getValue()));
                    }
                }
                if(collection.size() > 0)
                {
                    setFieldValue(item,map,collection,false);
                }
            }
            else if(map.isXMLNode())
            {
                String str = jsomItemScript.valueOf(map.getNames());
                if(null == str)
                {
                    continue;
                }
                try
                {
                    setFieldValue(item,map,XMLHelper.fromString(str),false);
                }
                catch(XMLException ex)
                {
                    //throw new ModelExceptioxn("Set field "+map.getName()+" Error ",ex);
                    ex.printStackTrace();
                }
            }
            else
            {
                String value = jsomItemScript.valueOf(map.getNames());
                setFieldValue(item,map,value,true);
            }
        }
        if(item instanceof IExModelItem)
        {
            ((IExModelItem) item).afterInitializedFromJSON(jsomItemScript);
            ((IExModelItem) item).normalizeModelItem();
        }
        else
        {
            //Risk way
            callUnknownMethod(item,FUN_AFTERINITIALIZEDFROMJSON,jsomItemScript);
            callUnknownMethod(item,FUN_NORMALIZEMODELITEM,null);
        }
        return(T) item;
    }

    public XMLNode toXML(Object obj)
        throws ModelException
    {
        if(null == obj)
        {
            return null;
        }
        MappedFieldItem currentItem = null; //exception
        try
        {
            MappedInformation info = this.getMappedInformation(obj.getClass());
            XMLNode nodeReturn = new XMLNode(info.getName());
            for(MappedFieldItem map : info.getMappedItems())
            {
                currentItem = map;
                Class<?> componentClass = map.getComponentClass();
                Object objValue = map.getValue(obj);
                String strName = map.getName();
                if(objValue == null)
                {
                    continue;
                }
                if(map.isCollection() && null != componentClass)
                {
                    Collection<?> coll = (Collection<?>) objValue;
                    XMLNode nodeParent = nodeReturn;
                    if(strName.indexOf(".") > 0)
                    {
                        nodeParent = nodeReturn.addNode(strName.substring(0,strName.indexOf(".")));
                        strName = strName.substring(strName.indexOf(".") + 1);
                    }
                    for(Object objFieldValue : coll)
                    {
                        if(null == objFieldValue)
                        {
                            continue;
                        }
                        if(isModelTagged(componentClass) || isModelItem(componentClass))
                        {
                            nodeParent.addNode(toXML(objFieldValue).setName(strName));
                        }
                        else if(StringPair.class.isAssignableFrom(componentClass))
                        {
                            StringPair ap = (StringPair) objFieldValue;
                            nodeParent.addNode(strName).addAttribute("key",ap.getKey(),"value",ap.getValue());
                        }
                        else
                        {
                            nodeParent.addNode(strName).addAttribute("value",Algorithms.toString(objFieldValue));
                        }
                    }
                }
                else //Normal Node
                {
                    String strValue = getValueString(objValue);
                    if(map.isXMLNode())
                    {
                        XMLNode node = XMLHelper.fromString(strValue).setName(strName);
                        nodeReturn.addNode(node);
                    }
                    else if(map.isCdata() || map.isNewNode())
                    {
                        //The content of this node is Text
                        XMLNode node = new XMLNode(strName);
                        if(map.isCdata())
                        {
                            node.setCData(strValue);
                        }
                        else
                        {
                            node.setInnerText(strValue);
                        }
                        nodeReturn.addNode(node);
                    }
                    else
                    {
                        if(isModelTagged(map.getType()) || isModelItem(map.getType()))
                        {
                            nodeReturn.addNode(toXML(objValue).setName(strName));
                        }
                        else if(StringPair.class.isAssignableFrom(map.getType()))
                        {
                            StringPair ap = (StringPair) objValue;
                            nodeReturn.addNode(strName).addAttribute("key",ap.getKey(),"value",ap.getValue());
                        }
                        else
                        {
                            nodeReturn.addAttribute(strName,strValue);
                        }
                    }
                }
            }
            if(obj instanceof IExModelItem)
            {
                ((IExModelItem) obj).afterConvertToXML(nodeReturn);
            }
            else
            {
                this.callUnknownMethod(obj,FUN_AFTERCONVERTTOXML,nodeReturn);
            }
            return nodeReturn;
        }
        catch(SecurityException ex)
        {
            throw new ModelException.FieldValueHandleError(obj,currentItem,ex);
        }
        catch(XMLException ex)
        {
            throw new ModelException.FieldValueHandleError(obj,currentItem,ex);
        }
    }

    public String getJSONString(Object obj)
        throws ModelException
    {
        JSONNode node = toJSONInnerProcess(obj,false,null);
        return null == node ? null : node.toJSON();
    }

    public String getJSONString(Object obj,IPostJSONHandler handler)
        throws ModelException
    {
        JSONNode node = toJSONInnerProcess(obj,false,handler);
        return null == node ? null : node.toJSON();
    }

    public JSONNode toJSON(Object object)
        throws ModelException
    {
        return toJSONInnerProcess(object,false,null);
    }

    public JSONNode toJSON(Object object,IPostJSONHandler handler)
        throws ModelException
    {
        return toJSONInnerProcess(object,false,handler);
    }

    @SuppressWarnings("rawtypes")
    private JSONNode toJSONInnerProcess(Object object,boolean enumAsInt,IPostJSONHandler handler)
        throws ModelException
    {
        if(null == object)
        {
            return null;
        }
        else if(object instanceof JSONNode)
        {
            return (JSONNode)object;
        }
        else if(object.getClass().isEnum())
        {
            if(enumAsInt)
            {
                //Get the ordinal of the enum, default implementation
                return new JSONConstant(((Enum) object).ordinal());
            }
            else
            {
                return new JSONConstant(((Enum) object).name());
            }
        }
        else if(object instanceof IntegerPair)
        {
            return new JSONConstant(IntegerPairHelper.getConstantFieldName((IntegerPair) object));
        }
        else if(DataTypes.isPrimitive(object.getClass()))
        {
            return new JSONConstant(object);
        }
        else if(DataTypes.isArray(object.getClass()))
        {
            JSONArray jsonary = new JSONArray();
            Object[] ary = Algorithms.toArray(object);
            if(null != ary)
            {
                for(Object obj : ary)
                {
                    jsonary.add(toJSONInnerProcess(obj,enumAsInt,handler));
                }
            }
            return jsonary;
        }
        else if(java.util.Map.class.isAssignableFrom(object.getClass()))
        {
            JSONClass cls = new JSONClass();
            java.util.Map<?,?> map = (java.util.Map) object;
            for(java.util.Map.Entry<?,?> e : map.entrySet())
            {
                String key = Algorithms.toString(e.getKey());
                if(Algorithms.isValidateSymbolName(key) == false)
                {
                    throw new ModelException.JSONNameConvertFailed(key);
                }
                cls.set(key,toJSONInnerProcess(e.getValue(),enumAsInt,handler));
            }
            return cls;
        }
        else
        {
            MappedFieldItem currentItem = null; //exception
            MappedInformation classMap = null;
            try
            {
                classMap = this.getMappedInformation(object.getClass());
                JSONClass nodeReturn = new JSONClass();
                for(MappedFieldItem fieldMap : classMap.getMappedItems())
                {
                    currentItem = fieldMap;
                    Object objValue = fieldMap.getValue(object);
                    String strName = fieldMap.getName();
                    nodeReturn.set(strName,toJSONInnerProcess(objValue,fieldMap.isEnumName() == false,handler));
                }
                if(object instanceof IExModelItem)
                {
                    ((IExModelItem) object).afterConvertToJSON(nodeReturn);
                }
                else
                {
                    this.callUnknownMethod(object,FUN_AFTERCONVERTTOJSON,nodeReturn);
                }
                if(null != handler)
                {
                    handler.toJSON(object,nodeReturn,classMap);
                }
                return nodeReturn;
            }
            catch(SecurityException ex)
            {
                throw new ModelException.FieldValueHandleError(object,currentItem,ex);
            }
        }
    }

    @SuppressWarnings("unchecked")
    public <T> T fromDatabase(Class<?> clsInit,Sqlca sqlca)
        throws ModelException
    {
        T t = (T) fromDatabase(clsInit,null == sqlca ? null : sqlca.getResultSet());
        return t;
    }

    @SuppressWarnings("unchecked")
    public <T> T fromDatabase(Class<?> clsInit,ResultSet rs)
        throws ModelException
    {
        MappedFieldItem currentItem = null; //exception
        if(null == clsInit || null == rs)
        {
            return null;
        }
        try
        {
            MappedInformation info = getMappedInformation(clsInit,(XMLNode)null);
            if(null == info.getMappedItems() || info.getMappedItems().size() < 1)
            {
                throw new ModelException.NoFieldMapped(clsInit);
            }
            Object item = constructorNewInstance(info.getOriginalClass());
            for(MappedFieldItem map : info.getMappedItems())
            {
                currentItem = map;
                if(map.isCollection())
                {
                    continue;
                }
                String columnName = map.getName();
                Object value = rs.getObject(columnName);
                if(map.isXMLNode())
                {
                    String xmlString = Algorithms.toString(value);
                    if(null == xmlString)
                    {
                        continue;
                    }
                    try
                    {
                        setFieldValue(item,map,XMLHelper.fromString(xmlString),false);
                    }
                    catch(XMLException ex)
                    {
                        //throw new ModelExceptioxn("Set field "+map.getName()+" Error ",ex);
                        ex.printStackTrace();
                    }
                }
                else
                {
                    setFieldValue(item,map,value,true);
                }
            }
            if(item instanceof IExModelItem)
            {
                ((IExModelItem) item).afterInitializedFromDatabase();
                ((IExModelItem) item).normalizeModelItem();
            }
            else
            {
                //Risk way
                callUnknownMethod(item,FUN_AFTERINITIALIZEDFROMDATABASE,rs);
                callUnknownMethod(item,FUN_NORMALIZEMODELITEM,null);
            }
            return(T) item;
        }
        catch(ModelException ex1)
        {
            throw ex1;
        }
        catch(SQLException ex1)
        {
            throw new ModelException.SQLExecutingFailed(currentItem,ex1);
        }
    }

    public int updateDatabase(Sqlca sqlca,Object obj)
        throws ModelException
    {
        if(null == obj || null == sqlca || sqlca.getConnection() == null)
        {
            return 0;
        }
        MappedFieldItem currentItem = null; //exception
        try
        {
            MappedInformation info = this.getMappedInformation(obj.getClass());
            StringBuffer strWhere = new StringBuffer();
            StringBuffer strSet = new StringBuffer();
            ArrayList<SqlParameter> parameters = new ArrayList<SqlParameter>();
            for(MappedFieldItem map : info.getMappedItems())
            {
                if(map.isCollection())
                {
                    continue;
                }
                currentItem = map;
                String strColumnName = map.getName();
                String strParameterName = "@" + map.getName();
                Object objValue = getFieldValueForDatabase(map,obj);
                parameters.add(new SqlParameter(strParameterName,objValue));
                if(map.isPrimaryKey())
                {
                    if(strWhere.length() > 0)
                    {
                        strWhere.append(" AND ");
                    }
                    strWhere.append(strColumnName + "=" + strParameterName);
                }
                else
                {
                    if(strSet.length() > 0)
                    {
                        strSet.append(" , ");
                    }
                    strSet.append(strColumnName + "=" + strParameterName);
                }
            }
            if(strWhere.length() < 1)
            {
                throw new ModelException.NoPrimaryKey(info.getOriginalClass());
            }
            if(strSet.length() < 1)
            {
                throw new ModelException.OnlyPrimaryKey(info.getOriginalClass());
            }
            String strSql = "UPDATE " + info.getName()
                + " SET " + strSet.toString()
                + " WHERE " + strWhere.toString();
            int n = sqlca.execute(strSql,parameters);
            sqlca.close();
            return n;
        }
        catch(SecurityException ex)
        {
            throw new ModelException.FieldValueHandleError(obj,currentItem,ex);
        }
        catch(SQLException ex)
        {
            throw new ModelException.SQLExecutingFailed(currentItem,ex);
        }
    }

    public int insertIntoDatabase(Sqlca sqlca,Object obj)
        throws ModelException
    {
        return insertIntoDatabase(sqlca,obj,null);
    }

    public int insertIntoDatabase(Sqlca sqlca,Object obj,String strTableName)
        throws ModelException
    {
        if(null == obj || null == sqlca || sqlca.getConnection() == null)
        {
            return 0;
        }
        MappedFieldItem currentItem = null; //exception
        try
        {
            MappedInformation info = this.getMappedInformation(obj.getClass());
            StringBuffer strField = new StringBuffer();
            StringBuffer strValues = new StringBuffer();
            ArrayList<SqlParameter> parameters = new ArrayList<SqlParameter>();
            for(MappedFieldItem map : info.getMappedItems())
            {
                if(map.isCollection())
                {
                    continue;
                }
                currentItem = map;
                String strColumnName = map.getName();
                String strParameterName = "@" + map.getName();
                Object objValue = getFieldValueForDatabase(map,obj);
                parameters.add(new SqlParameter(strParameterName,objValue));
                if(strField.length() > 0)
                {
                    strField.append(" , ");
                    strValues.append(" , ");
                }
                strField.append(strColumnName);
                strValues.append(strParameterName);
            }
            if(strValues.length() < 1)
            {
                throw new ModelException.NoFieldMapped(info.getOriginalClass());
            }
            String strSql = "INSERT INTO "
                + (Algorithms.isEmpty(strTableName) ? info.getName() : strTableName)
                + " ( " + strField.toString() + " ) VALUES ( " + strValues.toString() + ")";
            int n = sqlca.execute(strSql,parameters);
            sqlca.close();
            return n;
        }
        catch(SecurityException ex)
        {
            throw new ModelException.FieldValueHandleError(obj,currentItem,ex);
        }
        catch(SQLException ex)
        {
            throw new ModelException.SQLExecutingFailed(currentItem,ex);
        }
    }

    public int deleteFromDatabase(Sqlca sqlca,Object obj)
        throws ModelException
    {
        if(null == obj || null == sqlca || sqlca.getConnection() == null)
        {
            return 0;
        }
        MappedFieldItem currentItem = null; //exception
        try
        {
            MappedInformation info = this.getMappedInformation(obj.getClass());
            StringBuffer strWhere = new StringBuffer();
            ArrayList<SqlParameter> parameters = new ArrayList<SqlParameter>();
            for(MappedFieldItem map : info.getMappedItems())
            {
                if(map.isCollection())
                {
                    continue;
                }
                currentItem = map;
                String strColumnName = map.getName();
                String strParameterName = "@" + map.getName();
                Object objValue = getFieldValueForDatabase(map,obj);
                parameters.add(new SqlParameter(strParameterName,objValue));
                if(map.isPrimaryKey())
                {
                    if(strWhere.length() > 0)
                    {
                        strWhere.append(" AND ");
                    }
                    strWhere.append(strColumnName + "=" + strParameterName);
                }
            }
            if(strWhere.length() < 1)
            {
                throw new ModelException.NoPrimaryKey(info.getOriginalClass());
            }
            String strSql = "DELETE FROM " + info.getName() + " WHERE " + strWhere.toString();
            int n = sqlca.execute(strSql,parameters);
            sqlca.close();
            return n;
        }
        catch(SecurityException ex)
        {
            throw new ModelException.FieldValueHandleError(obj,currentItem,ex);
        }
        catch(SQLException ex)
        {
            throw new ModelException.SQLExecutingFailed(currentItem,ex);
        }
    }

    @SuppressWarnings("unchecked")
    public <T> T initFromDatabase(Sqlca sqlca,Object obj)
        throws ModelException
    {
        if(null == obj || null == sqlca || sqlca.getConnection() == null)
        {
            return null;
        }
        MappedFieldItem currentItem = null; //exception
        try
        {
            MappedInformation info = this.getMappedInformation(obj.getClass());
            StringBuffer strWhere = new StringBuffer();
            ArrayList<SqlParameter> parameters = new ArrayList<SqlParameter>();
            for(MappedFieldItem map : info.getMappedItems())
            {
                if(map.isCollection())
                {
                    continue;
                }
                currentItem = map;
                String strColumnName = map.getName();
                String strParameterName = "@" + map.getName();
                Object objValue = getFieldValueForDatabase(map,obj);
                parameters.add(new SqlParameter(strParameterName,objValue));
                if(map.isPrimaryKey())
                {
                    if(strWhere.length() > 0)
                    {
                        strWhere.append(" AND ");
                    }
                    strWhere.append(strColumnName + "=" + strParameterName);
                }
            }
            if(strWhere.length() < 1)
            {
                throw new ModelException.NoPrimaryKey(info.getOriginalClass());
            }
            String strSql = "SELECT * FROM " + info.getName() + " WHERE " + strWhere.toString();
            sqlca.execute(strSql,parameters);
            if(sqlca.next())
            {
                obj = this.fromDatabase(obj.getClass(),sqlca.getResultSet());
            }
            else
            {
                obj = null;
            }
            sqlca.close();
            return(T) obj;
        }
        catch(SecurityException ex)
        {
            throw new ModelException.FieldValueHandleError(obj,currentItem,ex);
        }
        catch(SQLException ex)
        {
            throw new ModelException.SQLExecutingFailed(currentItem,ex);
        }
    }

    @SuppressWarnings("unchecked")
	private Object getFieldValueForDatabase(MappedFieldItem map,Object obj)
        throws ModelException
    {
        Object objValue = map.getValue(obj);
        if(null == objValue)
        {
            return objValue;
        }
        if(map.isXMLNode())
        {
            objValue = Algorithms.toString(objValue);
        }
        else if(map.getType().isEnum())
        {
            if(map.isEnumName())
            {
                objValue = Algorithms.toString(objValue);
            }
            else
            {
                objValue = Algorithms.enumFrom((Class<Enum<?>>) map.getType(),Algorithms.toString(objValue)).ordinal();
            }
        }
        else if(IntegerPair.class.isAssignableFrom(map.getType()))
        {
            objValue = ((IntegerPair) objValue).getId();
        }
        else if(StringPair.class.isAssignableFrom(map.getType()))
        {
            objValue = ((StringPair) objValue).getId();
        }
        return objValue;
    }

    public Object getFieldValue(Object obj,String ...fieldNames)
        throws ModelException
    {
        if(null == obj)
        {
            return null;
        }
        else
        {
            MappedFieldItem item = null;
            for(String f : fieldNames)
            {
                item = this.getMappedInformation(obj.getClass()).getFieldMap(f);
                if(item != null)
                {
                    break;
                }
            }
            if(null == item)
            {
                throw new ModelException.MissingMandatoryField(obj,Algorithms.toString(fieldNames));
            }
            else
            {
                return item.getValue(obj);
            }
        }
    }

    @SuppressWarnings("unchecked")
    public <T> java.util.List<T> listFromJSON(Class<?> cls,JSONArray node)
        throws ModelException
    {
        if(null == cls || null == node)
        {
            return null;
        }
        java.util.List<T> ret = new java.util.ArrayList<T>();
        for(JSONNode ns : node.items())
        {
            if(ns.isJSONClass())
            {
                ret.add((T)this.fromJSON(cls,(JSONClass) ns));
            }
        }
        return ret;
    }

    @SuppressWarnings("unchecked")
    public <T> java.util.List<T> listFromXML(Class<?> cls,XMLNode node,String subNode)
        throws ModelException
    {
        if(null == cls || null == node)
        {
            return null;
        }
        java.util.List<T> ret = new java.util.ArrayList<T>();
        for(XMLNode ns : (Algorithms.isEmpty(subNode) ? node.selectNodes() : node.selectNodes(subNode)))
        {
            ret.add((T)this.fromXML(cls,ns));
        }
        return ret;
    }
}
